#include "ujson.h"
#include "yyjson.h"

#include <QDebug>
#include <QElapsedTimer>

class UJsonPrivate
{
    Q_DECLARE_PUBLIC(UJson)
public:
    UJsonPrivate(UJson* parent) : q_ptr(parent) {}
    ~UJsonPrivate() {}

    bool parseArray(QVariantList &list, yyjson_val* value);

    bool parseObject(QVariantMap &map, yyjson_val* value);

    void parseValue(QVariantMap &map, const QString &key, yyjson_val* value);

    void parseValue(QVariantList &list, yyjson_val* value);

private:
    UJson* q_ptr = nullptr;
    QVariantList jsonList;
    QVariantMap jsonMap;
};

bool UJsonPrivate::parseArray(QVariantList &list, yyjson_val* value)
{
    yyjson_val* val;
    yyjson_arr_iter iter;
    if (!yyjson_arr_iter_init(value, &iter))
    {
        qCritical() << "yyjson_arr_iter_init failed!!!";
        return false;
    }
    while ((val = yyjson_arr_iter_next(&iter)))
    {
        parseValue(list, val);
    }

    return true;
}

bool UJsonPrivate::parseObject(QVariantMap &map, yyjson_val* value)
{
    yyjson_obj_iter iter;
    if (!yyjson_obj_iter_init(value, &iter))
    {
        qCritical() << "yyjson_obj_iter_init failed!!!";
        return false;
    }
    yyjson_val* key, *val;
    while ((key = yyjson_obj_iter_next(&iter)))
    {
        val = yyjson_obj_iter_get_val(key);
        parseValue(map, yyjson_get_str(key), val);
    }

    return true;
}

void UJsonPrivate::parseValue(QVariantMap &map, const QString &key, yyjson_val* value)
{
    if (yyjson_is_uint(value))
    {
        map[key] = QVariant::fromValue(yyjson_get_uint(value));
    }
    else if (yyjson_is_sint(value))
    {
        map[key] = QVariant::fromValue(yyjson_get_sint(value));
    }
    else if (yyjson_is_str(value))
    {
        map[key] = QString(yyjson_get_str(value));
    }
    else if (yyjson_is_bool(value))
    {
        map[key] = yyjson_get_bool(value);
    }
    else if (yyjson_is_real(value))
    {
        map[key] = yyjson_get_real(value);
    }
    else if (yyjson_is_arr(value))
    {
        QVariantList valueList;
        yyjson_val* val;
        yyjson_arr_iter iter;
        yyjson_arr_iter_init(value, &iter);
        while ((val = yyjson_arr_iter_next(&iter)))
        {
            parseValue(valueList, val);
        }
        map[key] = QVariant::fromValue(valueList);
    }
    else if (yyjson_is_obj(value))
    {
        QVariantMap valueMap;
        yyjson_obj_iter iter;
        if (!yyjson_obj_iter_init(value, &iter))
        {
            qCritical() << "yyjson_obj_iter_init failed!!!";
            return;
        }
        yyjson_val* jsonKey, *val;
        while ((jsonKey = yyjson_obj_iter_next(&iter)))
        {
            val = yyjson_obj_iter_get_val(jsonKey);
            parseValue(valueMap, yyjson_get_str(jsonKey), val);
        }
        map[key] = QVariant::fromValue(valueMap);
    }
}

void UJsonPrivate::parseValue(QVariantList &list, yyjson_val* value)
{
    if (yyjson_is_uint(value))
    {
        list.append(QVariant::fromValue(yyjson_get_uint(value)));
    }
    else if (yyjson_is_sint(value))
    {
        list.append(QVariant::fromValue(yyjson_get_sint(value)));
    }
    else if (yyjson_is_str(value))
    {
        list.append(QString(yyjson_get_str(value)));
    }
    else if (yyjson_is_bool(value))
    {
        list.append(yyjson_get_bool(value));
    }
    else if (yyjson_is_real(value))
    {
        list.append(yyjson_get_real(value));
    }
    else if (yyjson_is_arr(value))
    {
        QVariantList valueList;
        yyjson_val* val;
        yyjson_arr_iter iter;
        yyjson_arr_iter_init(value, &iter);
        while ((val = yyjson_arr_iter_next(&iter)))
        {
            parseValue(valueList, val);
        }
        list.append(QVariant::fromValue(valueList));
    }
    else if (yyjson_is_obj(value))
    {
        QVariantMap valueMap;
        yyjson_obj_iter iter;
        if (!yyjson_obj_iter_init(value, &iter))
        {
            qCritical() << "yyjson_obj_iter_init failed!!!";
            return;
        }
        yyjson_val* jsonKey, *val;
        while ((jsonKey = yyjson_obj_iter_next(&iter)))
        {
            val = yyjson_obj_iter_get_val(jsonKey);
            parseValue(valueMap, yyjson_get_str(jsonKey), val);
        }
        list.append(QVariant::fromValue(valueMap));
    }
}

UJson::UJson() : d_ptr(new UJsonPrivate(this))
{
    Q_D(UJson);
}

UJson::~UJson()
{
}

bool UJson::fromJson(const QByteArray &json)
{
    Q_D(UJson);

    QElapsedTimer mstimer;
    mstimer.start();
    yyjson_doc* doc = yyjson_read(json.constData(), json.length(), 0);
    yyjson_val* root = yyjson_doc_get_root(doc);
    qInfo() << "json parse time:" << (double)mstimer.nsecsElapsed() / (double)1000000;

    if (nullptr == root)
    {
        yyjson_doc_free(doc);
        return false;
    }

    bool isOk = false;
    if (yyjson_is_arr(root))
    {
        d->jsonList.clear();
        isOk = d->parseArray(d->jsonList, root);
    }
    else if (yyjson_is_obj(root))
    {
        d->jsonMap.clear();
        isOk = d->parseObject(d->jsonMap, root);
    }
    else
    {
        isOk = false;
    }

    yyjson_doc_free(doc);
    return isOk;
}

bool UJson::fromFile(const QString &fileName)
{
    Q_D(UJson);

    QElapsedTimer mstimer;
    mstimer.start();
    yyjson_read_flag flg = YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS;
    yyjson_read_err err;
    yyjson_doc* doc = yyjson_read_file(fileName.toStdString().c_str(), flg, nullptr, &err);
    if (YYJSON_READ_SUCCESS != err.code)
    {
        qCritical() << "yyjson_read_file failed!!! code:" << err.code << "msg:" << err.msg;
        yyjson_doc_free(doc);
        return false;
    }
    yyjson_val* root = yyjson_doc_get_root(doc);
    qInfo() << "json parse time:" << (double)mstimer.nsecsElapsed() / (double)1000000;

    if (nullptr == root)
    {
        yyjson_doc_free(doc);
        return false;
    }

    bool isOk = false;
    if (yyjson_is_arr(root))
    {
        d->jsonList.clear();
        isOk = d->parseArray(d->jsonList, root);
    }
    else if (yyjson_is_obj(root))
    {
        d->jsonMap.clear();
        isOk = d->parseObject(d->jsonMap, root);
    }
    else
    {
        isOk = false;
    }

    yyjson_doc_free(doc);
    return isOk;
}

QVariantList UJson::getArrayList()
{
    Q_D(UJson);

    return d->jsonList;
}

QVariantMap UJson::getObjectMap()
{
    Q_D(UJson);

    return d->jsonMap;
}
